package com.clp.protocol.iec104.apdu;

import com.clp.protocol.iec104.apdu.apci.*;
import com.clp.protocol.iec104.apdu.asdu.Cot;
import com.clp.protocol.iec104.apdu.asdu.IAsdu;
import com.clp.protocol.iec104.apdu.asdu.Vsq;
import com.clp.protocol.iec104.apdu.asdu.info_obj.InfoObj;
import com.clp.protocol.iec104.apdu.asdu.info_obj.NcInfoObj;
import com.clp.protocol.iec104.apdu.asdu.info_obj.info_elem.C_DC_NA_1_InfoElem;
import com.clp.protocol.iec104.apdu.asdu.info_obj.info_elem.C_SC_NA_1_InfoElem;
import com.clp.protocol.iec104.apdu.asdu.info_obj.info_elem.C_SE_NA_1_InfoElem;
import com.clp.protocol.iec104.apdu.asdu.info_obj.info_elem.C_SE_NC_1_InfoElem;
import com.clp.protocol.iec104.apdu.asdu.info_obj.qua.C_CI_NA_1_Qua;
import com.clp.protocol.iec104.apdu.asdu.info_obj.qua.C_IC_NA_1_Qua;
import com.clp.protocol.iec104.apdu.asdu.info_obj.qua.C_SE_NA_1_Qua;
import com.clp.protocol.iec104.apdu.asdu.info_obj.qua.C_SE_NC_1_Qua;
import com.clp.protocol.iec104.definition.*;
import com.clp.protocol.iec104.definition.cot.Cause;
import com.clp.protocol.iec104.definition.cot.Pn;
import com.clp.protocol.iec104.definition.cot.Test;
import com.clp.protocol.iec104.definition.qua_type.TaQuaType;
import com.clp.protocol.iec104.definition.qua_type.TotalCallQuaType;
import com.clp.protocol.iec104.definition.qua_type.TpQuaType;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;

import java.util.ArrayList;
import java.util.List;

/**
 * Apdu 工厂
 */
public class ApduFactory {
    private static final boolean USE_SINGLETON_U_APDU = true; // 是否使用单例的 UApdu

    public static Apdu getApdu(ByteBuf buf) {
        checkApduBytebuf(buf);
        // 检查帧的类型
        byte[] ctrlAreaBytes = new byte[ConstVal.CTRL_AREA_LEN];
        buf.getBytes(buf.readerIndex() + ConstVal.HEAD_LEN + ConstVal.LENGTH_LEN, ctrlAreaBytes);
        ApduType apduType = ApduType.gain(ctrlAreaBytes);
        // 如果是 U帧，使用单例
        Apdu apdu = null;
        if (USE_SINGLETON_U_APDU && apduType == ApduType.UType) {
            buf.skipBytes(ConstVal.APCI_LEN); // 可以跳过这个帧的字节
            apdu = UCtrlType.gain(ctrlAreaBytes).getSingletonUApdu();
        } else {
            apdu = apduType.newInvalidApdu().initBy(buf);
        }
        if (buf.readableBytes() != 0) {
            throw new RuntimeException("未完全解析 ByteBuf ！");
        }
        return apdu;
    }

    public static Apdu getApdu(byte[] bytes) {
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer(bytes.length);
        Apdu apdu = getApdu(byteBuf.writeBytes(bytes));
        byteBuf.release();
        return apdu;
    }

    private static void checkApduBytebuf(ByteBuf buf) {
        int apciLen = ConstVal.HEAD_LEN + ConstVal.LENGTH_LEN + ConstVal.CTRL_AREA_LEN;
        if (buf.readableBytes() < apciLen) throw new RuntimeException("帧的长度小于APCI长度！");
        if (buf.getByte(buf.readerIndex()) != ConstVal.HEAD_VAL) {
            throw new RuntimeException("规约类型不是104");
        }
    }

    /**
     * 生产 U 类型的Apdu
     *
     * @param uCtrlType 控制帧类型
     * @return
     */
    public static UApdu getUApdu(UCtrlType uCtrlType) {
        // 如果是 U帧，可以使用单例
        if (USE_SINGLETON_U_APDU) {
            return uCtrlType.getSingletonUApdu();
        }
        return new UApdu(new UApci(ConstVal.HEAD_VAL, ConstVal.APCI_LEN, new UCtrlArea(uCtrlType)));
    }

    public static UApdu getUApduOfStartDtV() {
        return getUApdu(UCtrlType.U_START_DT_V);
    }

    public static UApdu getUApduOfStartDtC() {
        return getUApdu(UCtrlType.U_START_DT_C);
    }

    public static UApdu getUApduOfStopDtV() {
        return getUApdu(UCtrlType.U_STOP_DT_V);
    }

    public static UApdu getUApduOfStopDtC() {
        return getUApdu(UCtrlType.U_STOP_DT_C);
    }

    public static UApdu getUApduOfTestFrV() {
        return getUApdu(UCtrlType.U_TEST_FR_V);
    }

    public static UApdu getUApduOfTestFrC() {
        return getUApdu(UCtrlType.U_TEST_FR_C);
    }

    /**
     * 生产 S 类型的Apdu
     *
     * @param recvSeq 接收序号
     * @return
     */
    public static SApdu getSApdu(int recvSeq) {
        // 构建apci
        return new SApdu(new SApci(ConstVal.HEAD_VAL, ConstVal.CTRL_AREA_LEN, new SCtrlArea(recvSeq)));
    }

    /**
     * 构建I帧
     *
     * @param sendSeq         发视序号
     * @param recvSeq         接收序号
     * @param typeTag：类型标识
     * @param vsq：可变结构限定词
     * @param cot：传输原因
     * @param rtuAddr：rtu公共地址
     * @param infoObjs：信息对象列表
     * @return
     */
    public static IApdu getIApdu(int sendSeq, int recvSeq, TypeTag typeTag, Vsq vsq, Cot cot, int rtuAddr, List<InfoObj> infoObjs) {
        // 构建apci，长度设为0，在转为字节之后再赋值
        return new IApdu(
                new IApci(ConstVal.HEAD_VAL, 0, new ICtrlArea(sendSeq, recvSeq)),
                new IAsdu(typeTag, vsq, cot, rtuAddr, infoObjs)
        );
    }

    /**
     * 获取apdu：i帧，总召唤
     *
     * @param sendSeq      发送序号
     * @param recvSeq      接收序号
     * @param rtuAddr：公共地址
     * @return
     */
    public static IApdu getIApduOfTotalCall100(int sendSeq, int recvSeq, int rtuAddr) {
        TypeTag typeTag = TypeTag.C_IC_NA_1; // 类型标识为总召唤100
        Vsq vsq = new Vsq(Sq.NOT_CONTINUE, 1); // 非顺序，数量1
        Cot cot = new Cot(Test.NOT_TEST, Pn.PN_YES, Cause.COT_ACT); // 测试位0，PN位0，传输原因：激活
        List<InfoObj> infoObjs = new ArrayList<>();
        InfoObj infoObj = new NcInfoObj(0, null, new C_IC_NA_1_Qua(TotalCallQuaType.GROUP_TOTAL_CALL), null) {
            @Override
            public TypeTag typeTag() {
                return typeTag;
            }
        };
        infoObjs.add(infoObj);

        return getIApdu(sendSeq, recvSeq, typeTag, vsq, cot, rtuAddr, infoObjs);
    }

    /**
     * 电度量召唤
     *
     * @param sendSeq 发送序号
     * @param recvSeq 接收序号
     * @param rtuAddr 公共地址
     * @return
     */
    public static IApdu getIApduOfTotalCall101(int sendSeq, int recvSeq, int rtuAddr) {
        TypeTag typeTag = TypeTag.C_CI_NA_1; // 类型标识为总召唤101
        Vsq vsq = new Vsq(Sq.NOT_CONTINUE, 1); // 非顺序，数量1
        Cot cot = new Cot(Test.NOT_TEST, Pn.PN_YES, Cause.COT_ACT); // 测试位0，PN位0，传输原因：激活
        List<InfoObj> infoObjs = new ArrayList<>();
        InfoObj infoObj = new NcInfoObj(0, null,  // 总召唤信息对象地址为0
                // 限定词为总召唤限定词
                new C_CI_NA_1_Qua(TpQuaType.QCC), null) {
            @Override
            public TypeTag typeTag() {
                return typeTag;
            }
        };
        infoObjs.add(infoObj);

        return getIApdu(sendSeq, recvSeq, typeTag, vsq, cot, rtuAddr, infoObjs);
    }

    /**
     * 获取apdu：i帧，单点遥控
     *
     * @param sendSeq                 发送序号
     * @param recvSeq                 接收序号
     * @param rtuAddr：公共地址
     * @param infoObjAddr：遥控的信息体地址
     * @param cmdType：命令类型（选择或执行）
     * @param onePointSwitch：单点遥控开关状态
     * @return
     */
    public static Apdu getIApduOfOnePointControl(int sendSeq, int recvSeq, int rtuAddr, int infoObjAddr,
                                                 Tc.CmdType cmdType, Tc.OnePointSwitch onePointSwitch) {
        TypeTag typeTag = TypeTag.C_SC_NA_1; // 类型标识为单点命令，遥控
        Vsq vsq = new Vsq(Sq.NOT_CONTINUE, 1); // 非顺序，数量1
        Cot cot = new Cot(Test.NOT_TEST, Pn.PN_YES, Cause.COT_ACT); // 测试位0，PN为0，传输原因：激活
        List<InfoObj> infoObjs = new ArrayList<>();
        InfoObj infoObj = new NcInfoObj(infoObjAddr, new C_SC_NA_1_InfoElem(cmdType, onePointSwitch), null, null) {
            @Override
            public TypeTag typeTag() {
                return typeTag;
            }
        };
        infoObjs.add(infoObj);

        return getIApdu(sendSeq, recvSeq, typeTag, vsq, cot, rtuAddr, infoObjs);
    }

    /**
     * @param sendSeq                 发送序号
     * @param recvSeq                 接收序号
     * @param rtuAddr                 ：公共地址
     * @param addr：遥控的信息体地址
     * @param cmdType：命令类型（选择或执行）
     * @param twoPointSwitch：单点遥控开关状态
     * @return
     */
    public static Apdu getIApduOfTwoPointControl(int sendSeq, int recvSeq, int rtuAddr, int addr,
                                                 Tc.CmdType cmdType, Tc.TwoPointSwitch twoPointSwitch) {
        TypeTag typeTag = TypeTag.C_DC_NA_1; // 类型标识为双点命令，遥控
        Vsq vsq = new Vsq(Sq.NOT_CONTINUE, 1); // 非顺序，数量1
        Cot cot = new Cot(Test.NOT_TEST, Pn.PN_YES, Cause.COT_ACT); // 测试位0，PN为0，传输原因：激活
        List<InfoObj> infoObjs = new ArrayList<>();
        InfoObj infoObj = new NcInfoObj(addr, new C_DC_NA_1_InfoElem(cmdType, twoPointSwitch), null, null) {
            @Override
            public TypeTag typeTag() {
                return typeTag;
            }
        };
        infoObjs.add(infoObj);

        return getIApdu(sendSeq, recvSeq, typeTag, vsq, cot, rtuAddr, infoObjs);
    }

    /**
     * 构造遥调-归一化值帧
     *
     * @param sendSeq     发送序号
     * @param recvSeq     接收序号
     * @param rtuAddr     公共地址
     * @param infoObjAddr 信息对象地址
     * @param setVal      设置值
     * @param quaType     qua类型
     * @return
     */
    public static Apdu getIApduOfTaNormalized(int sendSeq, int recvSeq, int rtuAddr, int infoObjAddr, int setVal, TaQuaType quaType) {
        TypeTag typeTag = TypeTag.C_SE_NA_1; // 设定值命令，归一化值
        Vsq vsq = new Vsq(Sq.NOT_CONTINUE, 1); // 非顺序，数量1
        Cot cot = new Cot(Test.NOT_TEST, Pn.PN_YES, Cause.COT_ACT); // 测试位0，PN为0，传输原因：激活
        List<InfoObj> infoObjs = new ArrayList<>();
        InfoObj infoObj = new NcInfoObj(infoObjAddr, new C_SE_NA_1_InfoElem(setVal), new C_SE_NA_1_Qua(quaType), null) {
            @Override
            public TypeTag typeTag() {
                return typeTag;
            }
        };
        infoObjs.add(infoObj);

        return getIApdu(sendSeq, recvSeq, typeTag, vsq, cot, rtuAddr, infoObjs);
    }

    /**
     * 构建遥调-短浮点数帧
     *
     * @param sendSeq
     * @param recvSeq
     * @param rtuAddr
     * @param infoObjAddr
     * @param setVal
     * @param quaType
     * @return
     */
    public static Apdu getIApduOfTaFloat(int sendSeq, int recvSeq, int rtuAddr, int infoObjAddr, float setVal, TaQuaType quaType) {
        TypeTag typeTag = TypeTag.C_SE_NC_1; // 设定值命令，短浮点数
        Vsq vsq = new Vsq(Sq.NOT_CONTINUE, 1); // 非顺序，数量1
        Cot cot = new Cot(Test.NOT_TEST, Pn.PN_YES, Cause.COT_ACT); // 测试位0，PN为0，传输原因：激活
        List<InfoObj> infoObjs = new ArrayList<>();
        InfoObj infoObj = new NcInfoObj(infoObjAddr, new C_SE_NC_1_InfoElem(setVal), new C_SE_NC_1_Qua(quaType), null) {
            @Override
            public TypeTag typeTag() {
                return typeTag;
            }
        };
        infoObjs.add(infoObj);

        return getIApdu(sendSeq, recvSeq, typeTag, vsq, cot, rtuAddr, infoObjs);
    }
}
